
#include "Renderer.h"
#include <fstream>
#include <string>
#include <sstream>

namespace p2
{

Renderer::~Renderer()
{
    Renderer::deleteVbo(_vtUnitQuadVertexData11Id);
}

void Renderer::clear()
{
	glClearColor(_clearRgba[0], _clearRgba[1], _clearRgba[2], _clearRgba[3]);
    glClear(GL_COLOR_BUFFER_BIT);
}

void Renderer::setModelView(Matrix4<real> const& mv)
{
    Matrix4<real> mvp = (mv * _baseModelViewMatrix) * _projectionMatrix;
    glUniformMatrix4fv(getCurrentProgram().getMvpMatrixLoc(), 1, GL_FALSE, (GLfloat* )mvp);
}

GLuint Renderer::createVbo(const void* data, int size, GLenum target, GLenum usage)
{
	/*
	 * create vbo, activate, and upload data to video hardware
	 */
    GLuint vbo = 0;
    glGenBuffers(1, &vbo);
    glBindBuffer(target, vbo);
    glBufferData(target, size, data, usage);
	/*
	 * check data size in VBO is same as input array
	 */
    GLint bufferSize = 0;
    glGetBufferParameteriv(target, GL_BUFFER_SIZE, &bufferSize);
    ASSERTLOG(size == bufferSize, "Renderer::createVBO");
    if(size != bufferSize)
    {
        Renderer::deleteVbo(vbo);
    }
	glBindBuffer(target, 0);
    return vbo;
}

void Renderer::deleteVbo(GLuint& vertexBuffer)
{
    if ( vertexBuffer ) {
        glDeleteBuffers(1, &vertexBuffer);
        vertexBuffer = 0;
    }
}

/*
 * REF: http://developer.apple.com/library/ios/#documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html
 * REF: http://www.arcsynthesis.org/gltut/Basics/Tut02%20Vertex%20Attributes.html
 * REF: http://www.jayway.com/2009/12/04/opengl-es-tutorial-for-android-part-ii-building-a-polygon/
 */
void Renderer::render()
{
    for ( int prog = PROGRAM_VC; prog < NUM_PROGRAMS; prog++ ) {
        if ( _objects[prog].size() > 0 ) {

            switchProgram(prog);

            RenderObject::List::iterator iter = _objects[prog].begin();
            for (; iter != _objects[prog].end(); ++iter) {
                RenderObject& object = (*iter);

				Matrix4<real> mv = Matrix4<real>::makeIdentity();
                if ( _renderObjectCallback ) {
                    _renderObjectCallback(*this, prog, object, mv);
                }
                setModelView(mv);

				glEnableVertexAttribArray(getProgram(prog).getPositionLoc());
				glBindBuffer(GL_ARRAY_BUFFER, object.getVerticesId());
				GLint bufferSize = 0;
				glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &bufferSize);
				if ( !bufferSize ) {
					return ;
				}
				switch (prog) {
					case PROGRAM_VC: {
						glVertexAttribPointer(getProgram(prog).getPositionLoc(), 2, GL_FLOAT, GL_FALSE, sizeof(RenderVertexVC), (void*)offsetof(RenderVertexVC, _position));
						glEnableVertexAttribArray(getProgram(prog).getColourLoc());
						glVertexAttribPointer(getProgram (prog).getColourLoc(), 4, GL_FLOAT, GL_FALSE, sizeof(RenderVertexVC), (void*)offsetof(RenderVertexVC, _colour));
						object.setNumCoords(bufferSize / sizeof(RenderVertexVC));
					}
					break;
					case PROGRAM_VT: {
						glVertexAttribPointer(getProgram(prog).getPositionLoc(), 2, GL_FLOAT, GL_FALSE, sizeof(RenderVertexVT), (void*)offsetof(RenderVertexVT, _position));
						glEnableVertexAttribArray(getProgram(prog).getTexCoordLoc());
						glVertexAttribPointer(getProgram(prog).getTexCoordLoc(), 2, GL_FLOAT, GL_FALSE, sizeof(RenderVertexVT), (void*)offsetof(RenderVertexVT, _texCoord));
						object.setNumCoords(bufferSize / sizeof(RenderVertexVT));
					}
					break;
					case PROGRAM_VTC: {
						glVertexAttribPointer(_programs[prog].getPositionLoc(), 2, GL_FLOAT, GL_FALSE, sizeof(RenderVertexVTC), (void*)offsetof(RenderVertexVTC, _position));
						glEnableVertexAttribArray(_programs[prog].getColourLoc());
						glVertexAttribPointer(_programs[prog].getColourLoc(), 4, GL_FLOAT, GL_FALSE, sizeof(RenderVertexVTC), (void*)offsetof(RenderVertexVTC, _colour));
						glEnableVertexAttribArray(_programs[prog].getTexCoordLoc());
						glVertexAttribPointer(_programs[prog].getTexCoordLoc(), 2, GL_FLOAT, GL_FALSE, sizeof(RenderVertexVTC), (void*)offsetof(RenderVertexVTC, _texCoord));
						object.setNumCoords(bufferSize / sizeof(RenderVertexVTC));
					}
					break;
				}

				if ( prog == PROGRAM_VT || prog == PROGRAM_VTC ) {
					glActiveTexture(GL_TEXTURE0);
					glBindTexture(GL_TEXTURE_2D, object.getTexId());
					GLint textureUnit = 0;
					glUniform1iv(_programs[prog].getMapsLoc(), 1, &textureUnit);
				} else {
					glActiveTexture(0);
					glBindTexture(GL_TEXTURE_2D, 0);
				}

				glDrawArrays(object.getMode(), 0, object.getNumCoords());

				glBindBuffer(GL_ARRAY_BUFFER, 0);
                glDisableVertexAttribArray(getProgram(prog).getTexCoordLoc());
                glDisableVertexAttribArray(getProgram(prog).getColourLoc());
                glDisableVertexAttribArray(getProgram(prog).getPositionLoc());
            }
        }
    }
}

void Renderer::draw(GLenum mode, RenderVertexVC const* vertices, GLsizei count)
{
    glEnableVertexAttribArray(getCurrentProgram().getPositionLoc());
    glVertexAttribPointer(getCurrentProgram().getPositionLoc(), 2, GL_FLOAT, GL_FALSE, sizeof(RenderVertexVC), (void*)&vertices[0]._position);
    glEnableVertexAttribArray(getCurrentProgram().getColourLoc());
    glVertexAttribPointer(getCurrentProgram().getColourLoc(), 4, GL_FLOAT, GL_FALSE, sizeof(RenderVertexVC), (void*)&vertices[0]._colour);
    glDrawArrays(mode, 0, count);
    glDisableVertexAttribArray(getCurrentProgram().getColourLoc());
    glDisableVertexAttribArray(getCurrentProgram().getPositionLoc());
}

bool Renderer::loadFile(char const* file, String& out)
{
    std::ifstream f(file);
    if(!f.is_open()) {
        return false;
    }
    std::stringstream fileData;
    fileData << f.rdbuf();
    f.close();
    out = fileData.str();
    return true;
}

bool Renderer::loadShaders(uint32 type, char const* vertShaderFile, char const* fragShaderFile)
{
    GLuint vertShader, fragShader;

    // Create shader program.
    getProgram(type).setId(glCreateProgram());

    // Create and compile vertex shader.
    if (!compileShader(&vertShader, GL_VERTEX_SHADER, vertShaderFile)) {
        return false;
    }

    // Create and compile fragment shader.
    if (!compileShader(&fragShader, GL_FRAGMENT_SHADER, fragShaderFile)) {
        return false;
    }

    // Attach vertex shader to program.
    glAttachShader(getProgram(type).getId(), vertShader);

    // Attach fragment shader to program.
    glAttachShader(getProgram(type).getId(), fragShader);

    // Link program.
    if (!linkProgram(getProgram(type).getId())) {
        if (vertShader) {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (getProgram(type).getId()) {
            glDeleteProgram(getProgram(type).getId());
            getProgram(type).setId(0);
        }
        return false;
    }

    // Release vertex and fragment shaders.
    if (vertShader) {
        //glDetachShader(_programs[type]._id, vertShader);
        glDeleteShader(vertShader);
    }
    if (fragShader) {
        //glDetachShader(_programs[type]._id, fragShader);
        glDeleteShader(fragShader);
    }

    // Bind attribute locations.
    getProgram(type).setPositionLoc(glGetAttribLocation(getProgram(type).getId(), "a_position"));
    if ( type == PROGRAM_VC || type == PROGRAM_VTC ) {
    	getProgram(type).setColourLoc(glGetAttribLocation(getProgram(type).getId(), "a_colour"));
    }
    if ( type == PROGRAM_VT || type == PROGRAM_VTC ) {
    	getProgram(type).setTexCoordLoc(glGetAttribLocation(getProgram(type).getId(), "a_texCoord"));
    }
    // Get uniform locations.
    getProgram(type).setMvpMatrixLoc(glGetUniformLocation(getProgram(type).getId(), "u_mvpMatrix"));
    if ( type != PROGRAM_VC ) {
    	getProgram(type).setMapsLoc(glGetUniformLocation(getProgram(type).getId(), "u_texture"));
    }

    return true;
}

bool Renderer::compileShader(GLuint* shader, GLenum type, char const* file)
{
    GLint status;

    String data;
    if (!loadFile(file, data))
    {
        return false;
    }
    const GLchar *source = data.c_str();

    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);

#if defined(DEBUG)
    GLint logLength;
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 1) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
        LOGF("Shader compile log:\n%s", log);
        free(log);
    }
#endif

    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        glDeleteShader(*shader);
        return false;
    }
    return true;
}

bool Renderer::linkProgram(GLuint prog)
{
    GLint status;
    glLinkProgram(prog);

#if defined(DEBUG)
    GLint logLength = 0;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 1) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        LOGF("Program link log:\n%s", log);
        free(log);
    }
#endif

    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    if (status == 0) {
        return false;
    }

    return true;
}

bool Renderer::validateProgram(GLuint prog)
{
    GLint logLength, status;

    glValidateProgram(prog);
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        LOGF("Program validate log:\n%s", log);
        free(log);
    }

    glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
    if (status == 0) {
        return false;
    }

    return true;
}

} /* namespace p2 */
